/*
 * File     : AbstractTestNativeStringBuffer.java
 * Created  : 10 Nov 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.test.type;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.googlecode.dni.Allocator;
import com.googlecode.dni.DirectNativeInterface;
import com.googlecode.dni.internal.Testing;
import com.googlecode.dni.type.AutoFree;
import com.googlecode.dni.type.NativeString;
import com.googlecode.dni.type.NativeStringBuffer;

/**
 * Base class for testing {@link NativeStringBuffer}.
 *
 * @author Matthew Wilson
 */
public abstract class AbstractTestNativeStringBuffer
{

    static
    {
        Testing.init();
    }

    private static final String STRING_1 = "Hello";

    private static final String STRING_2 = ", World!";

    private static final String STRING_3 = "0123456789abcdef";

    private static final String STRING_1024;

    static
    {
        char[] array = new char[ 1024 ];
        Arrays.fill( array, 'a' );
        STRING_1024 = new String( array );
    }


    private final String charset;

    private final int unitSize;


    private final AutoFree autoFree = new AutoFree();

    private NativeString nativeString1;

    private NativeString nativeString2;

    private NativeString nativeString3;

    private NativeString nativeString1024;


    /**
     * @param charset
     *            the charset
     * @param unitSize
     *            the unit size (1, 2, or 4)
     */
    AbstractTestNativeStringBuffer( final String charset,
                                    final int unitSize )
    {
        this.charset = charset;
        this.unitSize = unitSize;
    }

    /** Tests the initial state. */
    @Test
    public void initialState()
    {
        NativeStringBuffer buffer = DirectNativeInterface.createStringBuffer( 1024, this.charset );
        this.autoFree.add( buffer );

        Assert.assertEquals( "Capacity", 1024, buffer.capacity() );
        Assert.assertEquals( "Length", 0, buffer.length() );
        Assert.assertEquals( "Terminator length", this.unitSize, buffer.terminatorLength() );
        Assert.assertEquals( "Remaining", 1024 - this.unitSize, buffer.remaining() );
        Assert.assertEquals( "Buffer content", "", buffer.read( this.charset ) );
    }

    /** Tests adding a native string to an empty buffer. */
    @Test
    public void addNativeStringToEmptyBuffer()
    {
        NativeStringBuffer buffer = DirectNativeInterface.createStringBuffer( 1024, this.charset );
        this.autoFree.add( buffer );

        buffer.append( this.nativeString1 );

        Assert.assertEquals( "Length", STRING_1.length() * (long) this.unitSize, buffer.length() );
        Assert.assertEquals( "Buffer content", STRING_1, buffer.read( this.charset ) );
    }

    /** Tests adding two native strings to an empty buffer. */
    @Test
    public void addTwoNativeStringsToEmptyBuffer()
    {
        NativeStringBuffer buffer = DirectNativeInterface.createStringBuffer( 1024, this.charset );
        this.autoFree.add( buffer );

        buffer.append( this.nativeString1 );
        buffer.append( this.nativeString2 );

        String string = STRING_1 + STRING_2;
        Assert.assertEquals( "Length", string.length() * (long) this.unitSize, buffer.length() );
        Assert.assertEquals( "Buffer content", string, buffer.read( this.charset ) );
    }

    /** Tests adding a Java string to an empty buffer. */
    @Test
    public void addJavaStringToEmptyBuffer()
    {
        NativeStringBuffer buffer = DirectNativeInterface.createStringBuffer( 1024, this.charset );
        this.autoFree.add( buffer );
        buffer.append( STRING_1 );

        Assert.assertEquals( "Length", STRING_1.length() * (long) this.unitSize, buffer.length() );
        Assert.assertEquals( "Buffer content", STRING_1, buffer.read( this.charset ) );
    }

    /** Tests adding two Java strings to an empty buffer. */
    @Test
    public void addTwoJavaStringsToEmptyBuffer()
    {
        NativeStringBuffer buffer = DirectNativeInterface.createStringBuffer( 1024, this.charset );
        this.autoFree.add( buffer );

        buffer.append( STRING_1 );
        buffer.append( STRING_2 );

        String string = STRING_1 + STRING_2;
        Assert.assertEquals( "Length", string.length() * (long) this.unitSize, buffer.length() );
        Assert.assertEquals( "Buffer content", string, buffer.read( this.charset ) );
    }

    /** Tests adding a Java string to a buffer with existing content. */
    @Test
    public void addJavaStringToExistingBuffer()
    {
        NativeStringBuffer buffer = DirectNativeInterface.createStringBuffer( 1024, this.charset );
        this.autoFree.add( buffer );

        // put some content in behind the scenes (just like native code would)
        ByteBuffer bufferView = buffer.pointer().getBuffer( 1024 );
        bufferView.put( Charset.forName( this.charset ).encode( CharBuffer.wrap( STRING_1 ) ) );
        bufferView.put( (byte) 0 );

        // now add some through the API... does it notice?
        buffer.append( this.nativeString3 );

        String string = STRING_1 + STRING_3;
        Assert.assertEquals( "Length", string.length() * (long) this.unitSize, buffer.length() );
        Assert.assertEquals( "Buffer content", string, buffer.read( this.charset ) );
    }

    /** Tests adding a native string to a buffer with insufficient space. */
    @Test
    public void addNativeStringToBufferWithoutSpace()
    {
        NativeStringBuffer buffer = DirectNativeInterface.createStringBuffer( 1024, this.charset );
        this.autoFree.add( buffer );

        buffer.append( this.nativeString1 );

        try
        {
            buffer.append( this.nativeString1024 );
            Assert.fail( "Expected IllegalArgumentException" );
        }
        catch ( IllegalArgumentException expected )
        {
            // expected
        }

        // original content must not have been modified (!)
        Assert.assertEquals( "Length", STRING_1.length() * (long) this.unitSize, buffer.length() );
        Assert.assertEquals( "Buffer content", STRING_1, buffer.read( this.charset ) );
    }

    /** Tests adding a Java string to a buffer with insufficient space. */
    @Test
    public void addJavaStringToBufferWithoutSpace()
    {
        NativeStringBuffer buffer = DirectNativeInterface.createStringBuffer( 1024, this.charset );
        this.autoFree.add( buffer );

        buffer.append( this.nativeString1 );

        try
        {
            buffer.append( STRING_1024 );
            Assert.fail( "Expected IllegalArgumentException" );
        }
        catch ( IllegalArgumentException expected )
        {
            // expected
        }

        // original content must not have been modified (!)
        Assert.assertEquals( "Length", STRING_1.length() * (long) this.unitSize, buffer.length() );
        Assert.assertEquals( "Buffer content", STRING_1, buffer.read( this.charset ) );
    }

    /** Initialises. */
    @Before
    public void before()
    {
        String string = STRING_1;
        this.nativeString1 = this.autoFree.add( wrapString( string ) );
        this.nativeString2 = this.autoFree.add( DirectNativeInterface.wrapString( STRING_2, this.charset ) );
        this.nativeString3 = this.autoFree.add( DirectNativeInterface.wrapString( STRING_3, this.charset ) );

        Allocator allocator = DirectNativeInterface.getAllocator();
        this.nativeString1024 = this.autoFree.add( DirectNativeInterface.wrapString( allocator.allocate( 1024 ) ) );
        ByteBuffer buffer = this.nativeString1024.pointer().getBuffer( 1024 );
        while ( buffer.hasRemaining() )
        {
            buffer.put( (byte) 255 );
        }
    }

    /** Tears down. */
    @After
    public void after()
    {
        this.autoFree.free();
    }

    private NativeString wrapString( final String string )
    {
        NativeString nativeString = DirectNativeInterface.wrapString( string, this.charset );
        Assert.assertEquals( "Wrapped string", string, nativeString.read( this.charset ) );
        return nativeString;
    }

}
